/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.actionbarsherlock.internal.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;

/**
 * This is a super-stripped version of LinearLayout from API 14 which supports
 * only horizontal layouts.
 */
public class IcsLinearLayout extends ViewGroup {

	private static final int[] LinearLayout = new int[] {
	/* 0 */android.R.attr.divider,
	/* 1 */android.R.attr.dividerPadding,
	/* 2 */android.R.attr.weightSum,
	/* 3 */android.R.attr.measureWithLargestChild,
	/* 4 */android.R.attr.showDividers,
	/* 5 */android.R.attr.baselineAlignedChildIndex, };
	private static final int LinearLayout_divider = 0;
	private static final int LinearLayout_dividerPadding = 1;
	private static final int LinearLayout_weightSum = 2;
	private static final int LinearLayout_measureWithLargestChild = 3;
	private static final int LinearLayout_showDividers = 4;
	private static final int LinearLayout_baselineAlignedChildIndex = 5;

	/**
	 * Don't show any dividers.
	 */
	public static final int SHOW_DIVIDER_NONE = 0;
	/**
	 * Show a divider at the beginning of the group.
	 */
	public static final int SHOW_DIVIDER_BEGINNING = 1;
	/**
	 * Show dividers between each item in the group.
	 */
	public static final int SHOW_DIVIDER_MIDDLE = 2;
	/**
	 * Show a divider at the end of the group.
	 */
	public static final int SHOW_DIVIDER_END = 4;

	/**
	 * Whether the children of this layout are baseline aligned. Only applicable
	 * if {@link #mOrientation} is horizontal.
	 */
	private boolean mBaselineAligned = true;

	/**
	 * If this layout is part of another layout that is baseline aligned, use
	 * the child at this index as the baseline.
	 * 
	 * Note: this is orthogonal to {@link #mBaselineAligned}, which is concerned
	 * with whether the children of this layout are baseline aligned.
	 */
	private int mBaselineAlignedChildIndex = -1;

	/**
	 * The additional offset to the child's baseline. We'll calculate the
	 * baseline of this layout as we measure vertically; for horizontal linear
	 * layouts, the offset of 0 is appropriate.
	 */
	private int mBaselineChildTop = 0;

	private int mGravity = Gravity.TOP;

	private int mTotalLength;

	private float mWeightSum;

	private boolean mUseLargestChild;

	private int[] mMaxAscent;
	private int[] mMaxDescent;

	private static final int VERTICAL_GRAVITY_COUNT = 4;

	private static final int INDEX_CENTER_VERTICAL = 0;
	private static final int INDEX_TOP = 1;
	private static final int INDEX_BOTTOM = 2;
	private static final int INDEX_FILL = 3;

	private Drawable mDivider;
	private int mDividerWidth;
	private int mShowDividers;
	private int mDividerPadding;

	public IcsLinearLayout(Context context) {
		super(context);
	}

	public IcsLinearLayout(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public IcsLinearLayout(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);

		TypedArray a = context.obtainStyledAttributes(attrs, /*
															 * com.android.internal
															 * .R.styleable.
															 */LinearLayout, defStyle, 0);

		// int index =
		// a.getInt(com.android.internal.R.styleable.LinearLayout_orientation,
		// -1);
		// if (index >= 0) {
		// setOrientation(index);
		// }

		// index =
		// a.getInt(com.android.internal.R.styleable.LinearLayout_gravity, -1);
		// if (index >= 0) {
		// setGravity(index);
		// }

		// boolean baselineAligned =
		// a.getBoolean(R.styleable.LinearLayout_baselineAligned, true);
		// if (!baselineAligned) {
		// setBaselineAligned(baselineAligned);
		// }

		mWeightSum = a.getFloat(/* com.android.internal.R.styleable. */LinearLayout_weightSum, -1.0f);

		mBaselineAlignedChildIndex = a.getInt(/* com.android.internal.R.styleable. */LinearLayout_baselineAlignedChildIndex, -1);

		mUseLargestChild = a.getBoolean(/* com.android.internal.R.styleable. */LinearLayout_measureWithLargestChild, false);

		setDividerDrawable(a.getDrawable(/* com.android.internal.R.styleable. */LinearLayout_divider));
		mShowDividers = a.getInt(/* com.android.internal.R.styleable. */LinearLayout_showDividers, SHOW_DIVIDER_NONE);
		mDividerPadding = a.getDimensionPixelSize(/*
												 * com.android.internal.R.styleable
												 * .
												 */LinearLayout_dividerPadding, 0);

		a.recycle();
	}

	/**
	 * Set how dividers should be shown between items in this layout
	 * 
	 * @param showDividers
	 *            One or more of {@link #SHOW_DIVIDER_BEGINNING},
	 *            {@link #SHOW_DIVIDER_MIDDLE}, or {@link #SHOW_DIVIDER_END}, or
	 *            {@link #SHOW_DIVIDER_NONE} to show no dividers.
	 */
	public void setShowDividers(int showDividers) {
		if (showDividers != mShowDividers) {
			requestLayout();
		}
		mShowDividers = showDividers;
	}

	// @Override
	public boolean shouldDelayChildPressedState() {
		return false;
	}

	/**
	 * @return A flag set indicating how dividers should be shown around items.
	 * @see #setShowDividers(int)
	 */
	public int getShowDividers() {
		return mShowDividers;
	}

	/**
	 * Set a drawable to be used as a divider between items.
	 * 
	 * @param divider
	 *            Drawable that will divide each item.
	 * @see #setShowDividers(int)
	 */
	public void setDividerDrawable(Drawable divider) {
		if (divider == mDivider) {
			return;
		}
		mDivider = divider;
		if (divider != null) {
			mDividerWidth = divider.getIntrinsicWidth();
		} else {
			mDividerWidth = 0;
		}
		setWillNotDraw(divider == null);
		requestLayout();
	}

	/**
	 * Set padding displayed on both ends of dividers.
	 * 
	 * @param padding
	 *            Padding value in pixels that will be applied to each end
	 * 
	 * @see #setShowDividers(int)
	 * @see #setDividerDrawable(Drawable)
	 * @see #getDividerPadding()
	 */
	public void setDividerPadding(int padding) {
		mDividerPadding = padding;
	}

	/**
	 * Get the padding size used to inset dividers in pixels
	 * 
	 * @see #setShowDividers(int)
	 * @see #setDividerDrawable(Drawable)
	 * @see #setDividerPadding(int)
	 */
	public int getDividerPadding() {
		return mDividerPadding;
	}

	/**
	 * Get the width of the current divider drawable.
	 * 
	 * @hide Used internally by framework.
	 */
	public int getDividerWidth() {
		return mDividerWidth;
	}

	@Override
	protected void onDraw(Canvas canvas) {
		if (mDivider == null) {
			return;
		}

		final int count = getVirtualChildCount();
		for (int i = 0; i < count; i++) {
			final View child = getVirtualChildAt(i);

			if (child != null && child.getVisibility() != GONE) {
				if (hasDividerBeforeChildAt(i)) {
					final LayoutParams lp = (LayoutParams) child.getLayoutParams();
					final int left = child.getLeft() - lp.leftMargin;
					drawVerticalDivider(canvas, left);
				}
			}
		}

		if (hasDividerBeforeChildAt(count)) {
			final View child = getVirtualChildAt(count - 1);
			int right = 0;
			if (child == null) {
				right = getWidth() - getPaddingRight() - mDividerWidth;
			} else {
				final LayoutParams lp = (LayoutParams) child.getLayoutParams();
				right = child.getRight() + lp.rightMargin;
			}
			drawVerticalDivider(canvas, right);
		}
	}

	void drawVerticalDivider(Canvas canvas, int left) {
		mDivider.setBounds(left, getPaddingTop() + mDividerPadding, left + mDividerWidth, getHeight() - getPaddingBottom() - mDividerPadding);
		mDivider.draw(canvas);
	}

	/**
	 * <p>
	 * Indicates whether widgets contained within this layout are aligned on
	 * their baseline or not.
	 * </p>
	 * 
	 * @return true when widgets are baseline-aligned, false otherwise
	 */
	public boolean isBaselineAligned() {
		return mBaselineAligned;
	}

	/**
	 * <p>
	 * Defines whether widgets contained in this layout are baseline-aligned or
	 * not.
	 * </p>
	 * 
	 * @param baselineAligned
	 *            true to align widgets on their baseline, false otherwise
	 * 
	 * @attr ref android.R.styleable#LinearLayout_baselineAligned
	 */
	public void setBaselineAligned(boolean baselineAligned) {
		mBaselineAligned = baselineAligned;
	}

	/**
	 * When true, all children with a weight will be considered having the
	 * minimum size of the largest child. If false, all children are measured
	 * normally.
	 * 
	 * @return True to measure children with a weight using the minimum size of
	 *         the largest child, false otherwise.
	 */
	public boolean isMeasureWithLargestChildEnabled() {
		return mUseLargestChild;
	}

	/**
	 * When set to true, all children with a weight will be considered having
	 * the minimum size of the largest child. If false, all children are
	 * measured normally.
	 * 
	 * Disabled by default.
	 * 
	 * @param enabled
	 *            True to measure children with a weight using the minimum size
	 *            of the largest child, false otherwise.
	 */
	public void setMeasureWithLargestChildEnabled(boolean enabled) {
		mUseLargestChild = enabled;
	}

	@Override
	public int getBaseline() {
		if (mBaselineAlignedChildIndex < 0) {
			return super.getBaseline();
		}

		if (getChildCount() <= mBaselineAlignedChildIndex) {
			throw new RuntimeException("mBaselineAlignedChildIndex of LinearLayout " + "set to an index that is out of bounds.");
		}

		final View child = getChildAt(mBaselineAlignedChildIndex);
		final int childBaseline = child.getBaseline();

		if (childBaseline == -1) {
			if (mBaselineAlignedChildIndex == 0) {
				// this is just the default case, safe to return -1
				return -1;
			}
			// the user picked an index that points to something that doesn't
			// know how to calculate its baseline.
			throw new RuntimeException("mBaselineAlignedChildIndex of LinearLayout " + "points to a View that doesn't know how to get its baseline.");
		}

		// TODO: This should try to take into account the virtual offsets
		// (See getNextLocationOffset and getLocationOffset)
		// We should add to childTop:
		// sum([getNextLocationOffset(getChildAt(i)) / i <
		// mBaselineAlignedChildIndex])
		// and also add:
		// getLocationOffset(child)
		int childTop = mBaselineChildTop;

		IcsLinearLayout.LayoutParams lp = (IcsLinearLayout.LayoutParams) child.getLayoutParams();
		return childTop + lp.topMargin + childBaseline;
	}

	/**
	 * @return The index of the child that will be used if this layout is part
	 *         of a larger layout that is baseline aligned, or -1 if none has
	 *         been set.
	 */
	public int getBaselineAlignedChildIndex() {
		return mBaselineAlignedChildIndex;
	}

	/**
	 * @param i
	 *            The index of the child that will be used if this layout is
	 *            part of a larger layout that is baseline aligned.
	 * 
	 * @attr ref android.R.styleable#LinearLayout_baselineAlignedChildIndex
	 */
	public void setBaselineAlignedChildIndex(int i) {
		if ((i < 0) || (i >= getChildCount())) {
			throw new IllegalArgumentException("base aligned child index out " + "of range (0, " + getChildCount() + ")");
		}
		mBaselineAlignedChildIndex = i;
	}

	/**
	 * <p>
	 * Returns the view at the specified index. This method can be overriden to
	 * take into account virtual children. Refer to
	 * {@link android.widget.TableLayout} and {@link android.widget.TableRow}
	 * for an example.
	 * </p>
	 * 
	 * @param index
	 *            the child's index
	 * @return the child at the specified index
	 */
	View getVirtualChildAt(int index) {
		return getChildAt(index);
	}

	/**
	 * <p>
	 * Returns the virtual number of children. This number might be different
	 * than the actual number of children if the layout can hold virtual
	 * children. Refer to {@link android.widget.TableLayout} and
	 * {@link android.widget.TableRow} for an example.
	 * </p>
	 * 
	 * @return the virtual number of children
	 */
	int getVirtualChildCount() {
		return getChildCount();
	}

	/**
	 * Returns the desired weights sum.
	 * 
	 * @return A number greater than 0.0f if the weight sum is defined, or a
	 *         number lower than or equals to 0.0f if not weight sum is to be
	 *         used.
	 */
	public float getWeightSum() {
		return mWeightSum;
	}

	/**
	 * Defines the desired weights sum. If unspecified the weights sum is
	 * computed at layout time by adding the layout_weight of each child.
	 * 
	 * This can be used for instance to give a single child 50% of the total
	 * available space by giving it a layout_weight of 0.5 and setting the
	 * weightSum to 1.0.
	 * 
	 * @param weightSum
	 *            a number greater than 0.0f, or a number lower than or equals
	 *            to 0.0f if the weight sum should be computed from the
	 *            children's layout_weight
	 */
	public void setWeightSum(float weightSum) {
		mWeightSum = Math.max(0.0f, weightSum);
	}

	/**
	 * Determines where to position dividers between children.
	 * 
	 * @param childIndex
	 *            Index of child to check for preceding divider
	 * @return true if there should be a divider before the child at childIndex
	 * @hide Pending API consideration. Currently only used internally by the
	 *       system.
	 */
	protected boolean hasDividerBeforeChildAt(int childIndex) {
		if (childIndex == 0) {
			return (mShowDividers & SHOW_DIVIDER_BEGINNING) != 0;
		} else if (childIndex == getChildCount()) {
			return (mShowDividers & SHOW_DIVIDER_END) != 0;
		} else if ((mShowDividers & SHOW_DIVIDER_MIDDLE) != 0) {
			boolean hasVisibleViewBefore = false;
			for (int i = childIndex - 1; i >= 0; i--) {
				if (getChildAt(i).getVisibility() != GONE) {
					hasVisibleViewBefore = true;
					break;
				}
			}
			return hasVisibleViewBefore;
		}
		return false;
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		mTotalLength = 0;
		int maxHeight = 0;
		int childState = 0;
		int alternativeMaxHeight = 0;
		int weightedMaxHeight = 0;
		boolean allFillParent = true;
		float totalWeight = 0;

		final int count = getVirtualChildCount();

		final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
		final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

		boolean matchHeight = false;

		if (mMaxAscent == null || mMaxDescent == null) {
			mMaxAscent = new int[VERTICAL_GRAVITY_COUNT];
			mMaxDescent = new int[VERTICAL_GRAVITY_COUNT];
		}

		final int[] maxAscent = mMaxAscent;
		final int[] maxDescent = mMaxDescent;

		maxAscent[0] = maxAscent[1] = maxAscent[2] = maxAscent[3] = -1;
		maxDescent[0] = maxDescent[1] = maxDescent[2] = maxDescent[3] = -1;

		final boolean baselineAligned = mBaselineAligned;
		final boolean useLargestChild = mUseLargestChild;

		final boolean isExactly = widthMode == MeasureSpec.EXACTLY;

		int largestChildWidth = Integer.MIN_VALUE;

		// See how wide everyone is. Also remember max height.
		for (int i = 0; i < count; ++i) {
			final View child = getVirtualChildAt(i);

			if (child == null) {
				mTotalLength += measureNullChild(i);
				continue;
			}

			if (child.getVisibility() == GONE) {
				i += getChildrenSkipCount(child, i);
				continue;
			}

			if (hasDividerBeforeChildAt(i)) {
				mTotalLength += mDividerWidth;
			}

			final IcsLinearLayout.LayoutParams lp = (IcsLinearLayout.LayoutParams) child.getLayoutParams();

			totalWeight += lp.weight;

			if (widthMode == MeasureSpec.EXACTLY && lp.width == 0 && lp.weight > 0) {
				// Optimization: don't bother measuring children who are going
				// to use
				// leftover space. These views will get measured again down
				// below if
				// there is any leftover space.
				if (isExactly) {
					mTotalLength += lp.leftMargin + lp.rightMargin;
				} else {
					final int totalLength = mTotalLength;
					mTotalLength = Math.max(totalLength, totalLength + lp.leftMargin + lp.rightMargin);
				}

				// Baseline alignment requires to measure widgets to obtain the
				// baseline offset (in particular for TextViews). The following
				// defeats the optimization mentioned above. Allow the child to
				// use as much space as it wants because we can shrink things
				// later (and re-measure).
				if (baselineAligned) {
					final int freeSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
					child.measure(freeSpec, freeSpec);
				}
			} else {
				int oldWidth = Integer.MIN_VALUE;

				if (lp.width == 0 && lp.weight > 0) {
					// widthMode is either UNSPECIFIED or AT_MOST, and this
					// child
					// wanted to stretch to fill available space. Translate that
					// to
					// WRAP_CONTENT so that it does not end up with a width of 0
					oldWidth = 0;
					lp.width = LayoutParams.WRAP_CONTENT;
				}

				// Determine how big this child would like to be. If this or
				// previous children have given a weight, then we allow it to
				// use all available space (and we will shrink things later
				// if needed).
				measureChildBeforeLayout(child, i, widthMeasureSpec, totalWeight == 0 ? mTotalLength : 0, heightMeasureSpec, 0);

				if (oldWidth != Integer.MIN_VALUE) {
					lp.width = oldWidth;
				}

				final int childWidth = child.getMeasuredWidth();
				if (isExactly) {
					mTotalLength += childWidth + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child);
				} else {
					final int totalLength = mTotalLength;
					mTotalLength = Math.max(totalLength, totalLength + childWidth + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
				}

				if (useLargestChild) {
					largestChildWidth = Math.max(childWidth, largestChildWidth);
				}
			}

			boolean matchHeightLocally = false;
			if (heightMode != MeasureSpec.EXACTLY && lp.height == LayoutParams.MATCH_PARENT) {
				// The height of the linear layout will scale, and at least one
				// child said it wanted to match our height. Set a flag
				// indicating that
				// we need to remeasure at least that view when we know our
				// height.
				matchHeight = true;
				matchHeightLocally = true;
			}

			final int margin = lp.topMargin + lp.bottomMargin;
			final int childHeight = child.getMeasuredHeight() + margin;
			childState = combineMeasuredStatesInt(childState, getMeasuredStateInt(child));

			if (baselineAligned) {
				final int childBaseline = child.getBaseline();
				if (childBaseline != -1) {
					// Translates the child's vertical gravity into an index
					// in the range 0..VERTICAL_GRAVITY_COUNT
					final int gravity = (lp.gravity < 0 ? mGravity : lp.gravity) & Gravity.VERTICAL_GRAVITY_MASK;
					final int index = ((gravity >> Gravity.AXIS_Y_SHIFT) & ~Gravity.AXIS_SPECIFIED) >> 1;

					maxAscent[index] = Math.max(maxAscent[index], childBaseline);
					maxDescent[index] = Math.max(maxDescent[index], childHeight - childBaseline);
				}
			}

			maxHeight = Math.max(maxHeight, childHeight);

			allFillParent = allFillParent && lp.height == LayoutParams.MATCH_PARENT;
			if (lp.weight > 0) {
				/*
				 * Heights of weighted Views are bogus if we end up remeasuring,
				 * so keep them separate.
				 */
				weightedMaxHeight = Math.max(weightedMaxHeight, matchHeightLocally ? margin : childHeight);
			} else {
				alternativeMaxHeight = Math.max(alternativeMaxHeight, matchHeightLocally ? margin : childHeight);
			}

			i += getChildrenSkipCount(child, i);
		}

		if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
			mTotalLength += mDividerWidth;
		}

		// Check mMaxAscent[INDEX_TOP] first because it maps to Gravity.TOP,
		// the most common case
		if (maxAscent[INDEX_TOP] != -1 || maxAscent[INDEX_CENTER_VERTICAL] != -1 || maxAscent[INDEX_BOTTOM] != -1 || maxAscent[INDEX_FILL] != -1) {
			final int ascent = Math.max(maxAscent[INDEX_FILL],
					Math.max(maxAscent[INDEX_CENTER_VERTICAL], Math.max(maxAscent[INDEX_TOP], maxAscent[INDEX_BOTTOM])));
			final int descent = Math.max(maxDescent[INDEX_FILL],
					Math.max(maxDescent[INDEX_CENTER_VERTICAL], Math.max(maxDescent[INDEX_TOP], maxDescent[INDEX_BOTTOM])));
			maxHeight = Math.max(maxHeight, ascent + descent);
		}

		if (useLargestChild && (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED)) {
			mTotalLength = 0;

			for (int i = 0; i < count; ++i) {
				final View child = getVirtualChildAt(i);

				if (child == null) {
					mTotalLength += measureNullChild(i);
					continue;
				}

				if (child.getVisibility() == GONE) {
					i += getChildrenSkipCount(child, i);
					continue;
				}

				final IcsLinearLayout.LayoutParams lp = (IcsLinearLayout.LayoutParams) child.getLayoutParams();
				if (isExactly) {
					mTotalLength += largestChildWidth + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child);
				} else {
					final int totalLength = mTotalLength;
					mTotalLength = Math.max(totalLength, totalLength + largestChildWidth + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
				}
			}
		}

		// Add in our padding
		mTotalLength += getPaddingLeft() + getPaddingRight();

		int widthSize = mTotalLength;

		// Check against our minimum width
		widthSize = Math.max(widthSize, getSuggestedMinimumWidth());

		// Reconcile our calculated size with the widthMeasureSpec
		int widthSizeAndState = resolveSizeAndStateInt(widthSize, widthMeasureSpec, 0);
		widthSize = widthSizeAndState & MEASURED_SIZE_MASK;

		// Either expand children with weight to take up available space or
		// shrink them if they extend beyond our current bounds
		int delta = widthSize - mTotalLength;
		if (delta != 0 && totalWeight > 0.0f) {
			float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;

			maxAscent[0] = maxAscent[1] = maxAscent[2] = maxAscent[3] = -1;
			maxDescent[0] = maxDescent[1] = maxDescent[2] = maxDescent[3] = -1;
			maxHeight = -1;

			mTotalLength = 0;

			for (int i = 0; i < count; ++i) {
				final View child = getVirtualChildAt(i);

				if (child == null || child.getVisibility() == View.GONE) {
					continue;
				}

				final IcsLinearLayout.LayoutParams lp = (IcsLinearLayout.LayoutParams) child.getLayoutParams();

				float childExtra = lp.weight;
				if (childExtra > 0) {
					// Child said it could absorb extra space -- give him his
					// share
					int share = (int) (childExtra * delta / weightSum);
					weightSum -= childExtra;
					delta -= share;

					final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTop() + getPaddingBottom() + lp.topMargin
							+ lp.bottomMargin, lp.height);

					// TODO: Use a field like lp.isMeasured to figure out if
					// this
					// child has been previously measured
					if ((lp.width != 0) || (widthMode != MeasureSpec.EXACTLY)) {
						// child was measured once already above ... base new
						// measurement
						// on stored values
						int childWidth = child.getMeasuredWidth() + share;
						if (childWidth < 0) {
							childWidth = 0;
						}

						child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY), childHeightMeasureSpec);
					} else {
						// child was skipped in the loop above. Measure for this
						// first time here
						child.measure(MeasureSpec.makeMeasureSpec(share > 0 ? share : 0, MeasureSpec.EXACTLY), childHeightMeasureSpec);
					}

					// Child may now not fit in horizontal dimension.
					childState = combineMeasuredStatesInt(childState, getMeasuredStateInt(child) & MEASURED_STATE_MASK);
				}

				if (isExactly) {
					mTotalLength += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child);
				} else {
					final int totalLength = mTotalLength;
					mTotalLength = Math
							.max(totalLength, totalLength + child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin + getNextLocationOffset(child));
				}

				boolean matchHeightLocally = heightMode != MeasureSpec.EXACTLY && lp.height == LayoutParams.MATCH_PARENT;

				final int margin = lp.topMargin + lp.bottomMargin;
				int childHeight = child.getMeasuredHeight() + margin;
				maxHeight = Math.max(maxHeight, childHeight);
				alternativeMaxHeight = Math.max(alternativeMaxHeight, matchHeightLocally ? margin : childHeight);

				allFillParent = allFillParent && lp.height == LayoutParams.MATCH_PARENT;

				if (baselineAligned) {
					final int childBaseline = child.getBaseline();
					if (childBaseline != -1) {
						// Translates the child's vertical gravity into an index
						// in the range 0..2
						final int gravity = (lp.gravity < 0 ? mGravity : lp.gravity) & Gravity.VERTICAL_GRAVITY_MASK;
						final int index = ((gravity >> Gravity.AXIS_Y_SHIFT) & ~Gravity.AXIS_SPECIFIED) >> 1;

						maxAscent[index] = Math.max(maxAscent[index], childBaseline);
						maxDescent[index] = Math.max(maxDescent[index], childHeight - childBaseline);
					}
				}
			}

			// Add in our padding
			mTotalLength += getPaddingLeft() + getPaddingRight();
			// TODO: Should we update widthSize with the new total length?

			// Check mMaxAscent[INDEX_TOP] first because it maps to Gravity.TOP,
			// the most common case
			if (maxAscent[INDEX_TOP] != -1 || maxAscent[INDEX_CENTER_VERTICAL] != -1 || maxAscent[INDEX_BOTTOM] != -1 || maxAscent[INDEX_FILL] != -1) {
				final int ascent = Math.max(maxAscent[INDEX_FILL],
						Math.max(maxAscent[INDEX_CENTER_VERTICAL], Math.max(maxAscent[INDEX_TOP], maxAscent[INDEX_BOTTOM])));
				final int descent = Math.max(maxDescent[INDEX_FILL],
						Math.max(maxDescent[INDEX_CENTER_VERTICAL], Math.max(maxDescent[INDEX_TOP], maxDescent[INDEX_BOTTOM])));
				maxHeight = Math.max(maxHeight, ascent + descent);
			}
		} else {
			alternativeMaxHeight = Math.max(alternativeMaxHeight, weightedMaxHeight);

			// We have no limit, so make all weighted views as wide as the
			// largest child.
			// Children will have already been measured once.
			if (useLargestChild && widthMode == MeasureSpec.UNSPECIFIED) {
				for (int i = 0; i < count; i++) {
					final View child = getVirtualChildAt(i);

					if (child == null || child.getVisibility() == View.GONE) {
						continue;
					}

					final IcsLinearLayout.LayoutParams lp = (IcsLinearLayout.LayoutParams) child.getLayoutParams();

					float childExtra = lp.weight;
					if (childExtra > 0) {
						child.measure(MeasureSpec.makeMeasureSpec(largestChildWidth, MeasureSpec.EXACTLY),
								MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(), MeasureSpec.EXACTLY));
					}
				}
			}
		}

		if (!allFillParent && heightMode != MeasureSpec.EXACTLY) {
			maxHeight = alternativeMaxHeight;
		}

		maxHeight += getPaddingTop() + getPaddingBottom();

		// Check against our minimum height
		maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());

		setMeasuredDimension(widthSizeAndState | (childState & MEASURED_STATE_MASK),
				resolveSizeAndStateInt(maxHeight, heightMeasureSpec, (childState << MEASURED_HEIGHT_STATE_SHIFT)));

		if (matchHeight) {
			forceUniformHeight(count, widthMeasureSpec);
		}
	}

	/**
	 * Merge two states as returned by {@link #getMeasuredState()}.
	 * 
	 * @param curState
	 *            The current state as returned from a view or the result of
	 *            combining multiple views.
	 * @param newState
	 *            The new view state to combine.
	 * @return Returns a new integer reflecting the combination of the two
	 *         states.
	 */
	public static int combineMeasuredStatesInt(int curState, int newState) {
		return curState | newState;
	}

	/**
	 * Version of {@link #resolveSizeAndState(int, int, int)} returning only the
	 * {@link #MEASURED_SIZE_MASK} bits of the result.
	 */
	public static int resolveSizeInt(int size, int measureSpec) {
		return resolveSizeAndStateInt(size, measureSpec, 0) & MEASURED_SIZE_MASK;
	}

	/**
	 * Utility to reconcile a desired size and state, with constraints imposed
	 * by a MeasureSpec. Will take the desired size, unless a different size is
	 * imposed by the constraints. The returned value is a compound integer,
	 * with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and
	 * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the resulting
	 * size is smaller than the size the view wants to be.
	 * 
	 * @param size
	 *            How big the view wants to be
	 * @param measureSpec
	 *            Constraints imposed by the parent
	 * @return Size information bit mask as defined by
	 *         {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}
	 *         .
	 */
	public static int resolveSizeAndStateInt(int size, int measureSpec, int childMeasuredState) {
		int result = size;
		int specMode = MeasureSpec.getMode(measureSpec);
		int specSize = MeasureSpec.getSize(measureSpec);
		switch (specMode) {
		case MeasureSpec.UNSPECIFIED:
			result = size;
			break;
		case MeasureSpec.AT_MOST:
			if (specSize < size) {
				result = specSize | MEASURED_STATE_TOO_SMALL;
			} else {
				result = size;
			}
			break;
		case MeasureSpec.EXACTLY:
			result = specSize;
			break;
		}
		return result | (childMeasuredState & MEASURED_STATE_MASK);
	}

	/**
	 * Return only the state bits of {@link #getMeasuredWidthAndState()} and
	 * {@link #getMeasuredHeightAndState()}, combined into one integer. The
	 * width component is in the regular bits {@link #MEASURED_STATE_MASK} and
	 * the height component is at the shifted bits
	 * {@link #MEASURED_HEIGHT_STATE_SHIFT}>>{@link #MEASURED_STATE_MASK}.
	 */
	public static int getMeasuredStateInt(View child) {
		return (child.getMeasuredWidth() & MEASURED_STATE_MASK)
				| ((child.getMeasuredHeight() >> MEASURED_HEIGHT_STATE_SHIFT) & (MEASURED_STATE_MASK >> MEASURED_HEIGHT_STATE_SHIFT));
	}

	private void forceUniformHeight(int count, int widthMeasureSpec) {
		// Pretend that the linear layout has an exact size. This is the
		// measured height of
		// ourselves. The measured height should be the max height of the
		// children, changed
		// to accomodate the heightMesureSpec from the parent
		int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY);
		for (int i = 0; i < count; ++i) {
			final View child = getVirtualChildAt(i);
			if (child.getVisibility() != GONE) {
				IcsLinearLayout.LayoutParams lp = (IcsLinearLayout.LayoutParams) child.getLayoutParams();

				if (lp.height == LayoutParams.MATCH_PARENT) {
					// Temporarily force children to reuse their old measured
					// width
					// FIXME: this may not be right for something like wrapping
					// text?
					int oldWidth = lp.width;
					lp.width = child.getMeasuredWidth();

					// Remeasure with new dimensions
					measureChildWithMargins(child, widthMeasureSpec, 0, uniformMeasureSpec, 0);
					lp.width = oldWidth;
				}
			}
		}
	}

	/**
	 * <p>
	 * Returns the number of children to skip after measuring/laying out the
	 * specified child.
	 * </p>
	 * 
	 * @param child
	 *            the child after which we want to skip children
	 * @param index
	 *            the index of the child after which we want to skip children
	 * @return the number of children to skip, 0 by default
	 */
	int getChildrenSkipCount(View child, int index) {
		return 0;
	}

	/**
	 * <p>
	 * Returns the size (width or height) that should be occupied by a null
	 * child.
	 * </p>
	 * 
	 * @param childIndex
	 *            the index of the null child
	 * @return the width or height of the child depending on the orientation
	 */
	int measureNullChild(int childIndex) {
		return 0;
	}

	/**
	 * <p>
	 * Measure the child according to the parent's measure specs. This method
	 * should be overriden by subclasses to force the sizing of children. This
	 * method is called by {@link #measureVertical(int, int)} and
	 * {@link #measureHorizontal(int, int)}.
	 * </p>
	 * 
	 * @param child
	 *            the child to measure
	 * @param childIndex
	 *            the index of the child in this view
	 * @param widthMeasureSpec
	 *            horizontal space requirements as imposed by the parent
	 * @param totalWidth
	 *            extra space that has been used up by the parent horizontally
	 * @param heightMeasureSpec
	 *            vertical space requirements as imposed by the parent
	 * @param totalHeight
	 *            extra space that has been used up by the parent vertically
	 */
	void measureChildBeforeLayout(View child, int childIndex, int widthMeasureSpec, int totalWidth, int heightMeasureSpec, int totalHeight) {
		measureChildWithMargins(child, widthMeasureSpec, totalWidth, heightMeasureSpec, totalHeight);
	}

	/**
	 * <p>
	 * Return the location offset of the specified child. This can be used by
	 * subclasses to change the location of a given widget.
	 * </p>
	 * 
	 * @param child
	 *            the child for which to obtain the location offset
	 * @return the location offset in pixels
	 */
	int getLocationOffset(View child) {
		return 0;
	}

	/**
	 * <p>
	 * Return the size offset of the next sibling of the specified child. This
	 * can be used by subclasses to change the location of the widget following
	 * <code>child</code>.
	 * </p>
	 * 
	 * @param child
	 *            the child whose next sibling will be moved
	 * @return the location offset of the next child in pixels
	 */
	int getNextLocationOffset(View child) {
		return 0;
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		final boolean isLayoutRtl = false;
		final int paddingTop = getPaddingTop();

		int childTop;
		int childLeft;

		// Where bottom of child should go
		final int height = getBottom() - getTop();
		int childBottom = height - getPaddingBottom();

		// Space available for child
		int childSpace = height - paddingTop - getPaddingBottom();

		final int count = getVirtualChildCount();

		// final int majorGravity = mGravity &
		// Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
		final int minorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;

		final boolean baselineAligned = mBaselineAligned;

		final int[] maxAscent = mMaxAscent;
		final int[] maxDescent = mMaxDescent;

		childLeft = getPaddingLeft();

		int start = 0;
		int dir = 1;
		// In case of RTL, start drawing from the last child.
		if (isLayoutRtl) {
			start = count - 1;
			dir = -1;
		}

		for (int i = 0; i < count; i++) {
			int childIndex = start + dir * i;
			final View child = getVirtualChildAt(childIndex);

			if (child == null) {
				childLeft += measureNullChild(childIndex);
			} else if (child.getVisibility() != GONE) {
				final int childWidth = child.getMeasuredWidth();
				final int childHeight = child.getMeasuredHeight();
				int childBaseline = -1;

				final IcsLinearLayout.LayoutParams lp = (IcsLinearLayout.LayoutParams) child.getLayoutParams();

				if (baselineAligned && lp.height != LayoutParams.MATCH_PARENT) {
					childBaseline = child.getBaseline();
				}

				int gravity = lp.gravity;
				if (gravity < 0) {
					gravity = minorGravity;
				}

				switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
				case Gravity.TOP:
					childTop = paddingTop + lp.topMargin;
					if (childBaseline != -1) {
						childTop += maxAscent[INDEX_TOP] - childBaseline;
					}
					break;

				case Gravity.CENTER_VERTICAL:
					// Removed support for baseline alignment when
					// layout_gravity or
					// gravity == center_vertical. See bug #1038483.
					// Keep the code around if we need to re-enable this feature
					// if (childBaseline != -1) {
					// // Align baselines vertically only if the child is
					// smaller than us
					// if (childSpace - childHeight > 0) {
					// childTop = paddingTop + (childSpace / 2) - childBaseline;
					// } else {
					// childTop = paddingTop + (childSpace - childHeight) / 2;
					// }
					// } else {
					childTop = paddingTop + ((childSpace - childHeight) / 2) + lp.topMargin - lp.bottomMargin;
					break;

				case Gravity.BOTTOM:
					childTop = childBottom - childHeight - lp.bottomMargin;
					if (childBaseline != -1) {
						int descent = child.getMeasuredHeight() - childBaseline;
						childTop -= (maxDescent[INDEX_BOTTOM] - descent);
					}
					break;
				default:
					childTop = paddingTop;
					break;
				}

				if (hasDividerBeforeChildAt(childIndex)) {
					childLeft += mDividerWidth;
				}

				childLeft += lp.leftMargin;
				setChildFrame(child, childLeft + getLocationOffset(child), childTop, childWidth, childHeight);
				childLeft += childWidth + lp.rightMargin + getNextLocationOffset(child);

				i += getChildrenSkipCount(child, childIndex);
			}
		}
	}

	private void setChildFrame(View child, int left, int top, int width, int height) {
		child.layout(left, top, left + width, top + height);
	}

	/**
	 * Describes how the child views are positioned. Defaults to GRAVITY_TOP. If
	 * this layout has a VERTICAL orientation, this controls where all the child
	 * views are placed if there is extra vertical space. If this layout has a
	 * HORIZONTAL orientation, this controls the alignment of the children.
	 * 
	 * @param gravity
	 *            See {@link android.view.Gravity}
	 * 
	 * @attr ref android.R.styleable#LinearLayout_gravity
	 */
	public void setGravity(int gravity) {
		if (mGravity != gravity) {
			if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
				gravity |= Gravity.TOP;
			}

			mGravity = gravity;
			requestLayout();
		}
	}

	@Override
	public LayoutParams generateLayoutParams(AttributeSet attrs) {
		return new IcsLinearLayout.LayoutParams(getContext(), attrs);
	}

	/**
	 * Returns a set of layout parameters with a width of
	 * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} and a height of
	 * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} when the
	 * layout's orientation is {@link #VERTICAL}. When the orientation is
	 * {@link #HORIZONTAL}, the width is set to
	 * {@link LayoutParams#WRAP_CONTENT} and the height to
	 * {@link LayoutParams#WRAP_CONTENT}.
	 */
	@Override
	protected LayoutParams generateDefaultLayoutParams() {
		return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
	}

	@Override
	protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
		return new LayoutParams(p);
	}

	// Override to allow type-checking of LayoutParams.
	@Override
	protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
		return p instanceof IcsLinearLayout.LayoutParams;
	}

	/**
	 * Per-child layout information associated with ViewLinearLayout.
	 * 
	 * @attr ref android.R.styleable#LinearLayout_Layout_layout_weight
	 * @attr ref android.R.styleable#LinearLayout_Layout_layout_gravity
	 */
	public static class LayoutParams extends ViewGroup.MarginLayoutParams {
		/**
		 * Indicates how much of the extra space in the LinearLayout will be
		 * allocated to the view associated with these LayoutParams. Specify 0
		 * if the view should not be stretched. Otherwise the extra pixels will
		 * be pro-rated among all views whose weight is greater than 0.
		 */
		public float weight;

		/**
		 * Gravity for the view associated with these LayoutParams.
		 * 
		 * @see android.view.Gravity
		 */
		public int gravity = -1;

		/**
		 * {@inheritDoc}
		 */
		public LayoutParams(Context c, AttributeSet attrs) {
			super(c, attrs);
			// TypedArray a =
			// c.obtainStyledAttributes(attrs,
			// com.android.internal.R.styleable.LinearLayout_Layout);

			weight = 0;// a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight,
						// 0);
			gravity = -1;// a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity,
							// -1);

			// a.recycle();
		}

		/**
		 * {@inheritDoc}
		 */
		public LayoutParams(int width, int height) {
			super(width, height);
			weight = 0;
		}

		/**
		 * Creates a new set of layout parameters with the specified width,
		 * height and weight.
		 * 
		 * @param width
		 *            the width, either {@link #MATCH_PARENT},
		 *            {@link #WRAP_CONTENT} or a fixed size in pixels
		 * @param height
		 *            the height, either {@link #MATCH_PARENT},
		 *            {@link #WRAP_CONTENT} or a fixed size in pixels
		 * @param weight
		 *            the weight
		 */
		public LayoutParams(int width, int height, float weight) {
			super(width, height);
			this.weight = weight;
		}

		/**
		 * {@inheritDoc}
		 */
		public LayoutParams(ViewGroup.LayoutParams p) {
			super(p);
		}

		/**
		 * {@inheritDoc}
		 */
		public LayoutParams(MarginLayoutParams source) {
			super(source);
		}
	}
}
